package cn.tedu.opencv.service;

import cn.tedu.common.ex.ResultCodeEnum;
import cn.tedu.common.ex.ServiceException;
import cn.tedu.opencv.params.RemoveParam;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.*;
import org.opencv.features2d.BFMatcher;
import org.opencv.features2d.SIFT;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.stream.Collectors;

@Service(value = "custom")
@Slf4j
public class CustomImgDetectionServiceImpl implements ImgDetectionService{

    private static final float RATIO_THRESHOLD = 0.6f;
    private static final Map<Mat, String> matAndFileNameMap = new LinkedHashMap<>();

    @Override
    public List<String> detect(List<MultipartFile> files, String selectPath, double percentage) {
        // 初始化系统
        initSystem();
        // 检查传入文件夹
        createIfNotExist(selectPath);
        // 加载图像并填充map
        List<Mat> images = loadImages(files);
        // 分组相似图像
        List<List<Mat>> imageGroups = groupImagesWithSIFT(images, percentage);

        // 在每个组中找到最清晰的图像
        List<String> outputPathList = new ArrayList<>();
        for (List<Mat> group : imageGroups) {
            Mat clearestImage = findClearestImage(group);
            if (clearestImage != null) {
                String outputPath = saveImgInSelectedFolder(clearestImage, selectPath);
                outputPathList.add(outputPath);
            }
        }
        return outputPathList;
    }

    @Override
    public List<String> group(List<MultipartFile> files, String selectPath, double percentage) {
        initSystem();

        createIfNotExist(selectPath);

        // 加载图像并填充map
        List<Mat> images = loadImages(files);
        // 分组相似图像
        List<List<Mat>> imageGroups = groupImagesWithSIFT(images, percentage);
        return saveByGroup(selectPath, imageGroups);
    }

    @Override
    public List<String> findClearestInGroups(String folderPath) {
        initSystem();

        // imread()函数读取文件绝对路径参数，参数中文件夹不能有中文，文件名不能有中文，可以允许空格，但是不允许中文和空格
        // Mat file = imread(folderPath);
        File folder = new File(folderPath);
        File[] files = folder.listFiles();
        if (files!= null) {
            List<String> outputPathList = new ArrayList<>();
            for (File file : files) {
                if (file.isDirectory()) {
                    File subfolder = new File(file.getAbsolutePath());
                    File[] images = subfolder.listFiles();
                    if (images!= null) {
                        List<Mat> loadImages = new ArrayList<>();
                        for (File image : images) {
                            if (image.isFile()) {
                                String inputFileName = image.getName();
                                byte[] bytes = new byte[0];
                                try {
                                    bytes = Files.readAllBytes(image.toPath());
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                MatOfByte matOfByte = new MatOfByte(bytes);
                                // 转化为Mat并填充matAndFileNameMap
                                Mat mat = Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_COLOR);
                                if (!mat.empty()) {
                                    loadImages.add(mat);
                                    matAndFileNameMap.put(mat, inputFileName);
                                } else {
                                    log.error("Error loading image: {}", matOfByte);
                                }
                            }
                        }

                        // 找到最清晰的图像
                        Mat clearestImage = findClearestImage(loadImages);
                        String outputPath = saveImgInSelectedFolder(clearestImage, folderPath + "\\");
                        outputPathList.add(outputPath);
                    }
                }
            }
            return outputPathList;
        }
        throw new ServiceException(ResultCodeEnum.PARAMETER_ERROR, "所选文件夹为空！");
    }

    @Override
    public String removeBlurryImages(RemoveParam removeParam) {

        initSystem();

        List<MultipartFile> files = removeParam.getFiles();
        String selectPath = removeParam.getSelectPath();
        Integer sharpnessThreshold = removeParam.getSharpnessThreshold();

        createIfNotExist(selectPath);
        try {
            Files.createDirectories(Paths.get(selectPath + "\\达标图片"));
            log.info("创建文件夹成功：{}", selectPath + "\\达标图片");
            Files.createDirectories(Paths.get(selectPath + "\\不达标图片"));
            log.info("创建文件夹成功：{}", selectPath + "\\不达标图片");
        } catch (IOException e) {
            throw new ServiceException(ResultCodeEnum.PARAMETER_ERROR, "创建文件夹失败！" + e.getMessage());
        }
        // 加载图片并计算清晰度评分
        List<Mat> matList = loadImages(files);
        List<Double> sharpnessScores = matList.stream().map(this::calculateSharpness).collect(Collectors.toList());

        // 根据清晰度评分归类
        for (int i = 0; i < sharpnessScores.size(); i++) {
            Mat mat = matList.get(i);
            log.info("阈值：{}", sharpnessScores.get(i));
            if (sharpnessScores.get(i) >= sharpnessThreshold) {
                saveImgInSelectedFolder(mat, selectPath + "\\达标图片\\");
            } else {
                saveImgInSelectedFolder(mat, selectPath + "\\不达标图片\\");
            }
        }
        return "已将清晰度达标图片放入：【"+ selectPath + "\\达标图片】；" + "不达标图片放入：【" + selectPath + "\\不达标图片】";
    }

    // 解决awt报错问题以及加载opencv动态库
    private void initSystem() {
        // 解决awt报错问题
        System.setProperty("java.awt.headless", "false");

        // 获取临时文件夹路径
        String tempFolderPath = System.getProperty("java.io.tmpdir");

        File tempFile = findOrCopyLibrary(tempFolderPath);

        // 加载临时文件
        if (tempFile != null) {
            log.info("文件绝对路径：{}", tempFile.getAbsolutePath());
            System.load(tempFile.getAbsolutePath());
        } else {
            log.error("无法找到或复制OpenCV库文件");
        }
    }

    // 在传入文件夹中找出opencv_java3416.dll文件（找不到就创建）
    private File findOrCopyLibrary(String tempFolderPath) {
        File folder = new File(tempFolderPath);
        File[] files = folder.listFiles();

        // 在临时文件夹中找到以opencv_java3416开头，以.dll结尾的文件
        if (files != null) {
            for (File file : files) {
                if (file.isFile() && file.getName().startsWith("opencv_java3416") && file.getName().endsWith(".dll")) {
                    return file;
                }
            }
        }

        // 如果上面没有找到，则自己创建临时文件
        return createTempLibraryFile();
    }

    // 在临时文件夹中创建opencv_java3416.dll文件
    private File createTempLibraryFile() {
        try (InputStream inputStream = getClass().getResourceAsStream("/lib/opencv/opencv_java3416.dll")) {
            log.info("读入文件流：{}", inputStream);

            if (inputStream != null) {
                File tempFile = File.createTempFile("opencv_java3416", ".dll");
                tempFile.deleteOnExit();

                try (FileOutputStream out = new FileOutputStream(tempFile)) {
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        out.write(buffer, 0, bytesRead);
                    }
                }

                return tempFile;
            } else {
                log.error("无法读取OpenCV库文件资源");
            }
        } catch (FileNotFoundException e) {
            log.error("找不到文件: {}", e.getMessage(), e);
        } catch (IOException e) {
            log.error("处理文件操作时发生异常: {}", e.getMessage(), e);
        }

        return null;
    }


    // 判断传入文件夹是否存在，不存在则创建
    private void createIfNotExist(String selectPath) {
        if (Files.exists(Paths.get(selectPath))) {
            File folder = new File(selectPath);
            if(Objects.requireNonNull(folder.listFiles()).length > 0) {
                throw new ServiceException(ResultCodeEnum.PARAMETER_ERROR, "文件夹不为空！请选择空白文件夹");
            }
        } else {
            try {
                Files.createDirectories(Paths.get(selectPath));
                log.info("创建文件夹成功：{}", selectPath);
            } catch (IOException e) {
                throw new ServiceException(ResultCodeEnum.PARAMETER_ERROR, "创建文件夹失败！");
            }
        }
    }

    // 在这里可以处理每个组中最清晰的图像，例如保存到文件、显示等
    private String saveImgInSelectedFolder(Mat clearestImage, String outputFolder) {
        // 获取输入文件的名称
        String inputFileName = matAndFileNameMap.get(clearestImage);
        // 获取输入文件名后缀
        String inputFileSuffix = inputFileName.substring(inputFileName.lastIndexOf("."));
        // 构造输出文件的路径
        String outputPath = outputFolder + inputFileName;
        // 编码图像为字节数组
        MatOfByte matOfByte = new MatOfByte();
        Imgcodecs.imencode(inputFileSuffix, clearestImage, matOfByte);
        // 将字节数组写入文件
        Path path = Paths.get(outputPath);
        try {
            Files.write(path, matOfByte.toArray(), StandardOpenOption.CREATE);
            log.info("Clearest image saved to: {}", outputPath);
            return outputPath;
        } catch (IOException e) {
            log.error("Error writing clearest image to file", e);
        }
        return null;
    }

    private List<String> saveByGroup(String selectPath, List<List<Mat>> imageGroups) {
        List<String> outputPathList = new ArrayList<>();

        for (int i = 0; i < imageGroups.size(); i++) {
            String outputFolder = selectPath + "group" + (i + 1) + "\\";

            try {
                Files.createDirectories(Paths.get(outputFolder));
            } catch (IOException e) {
                log.error("Error creating output folder: {}", outputFolder, e);
                continue;
            }

            for (Mat mat : imageGroups.get(i)) {
                String outputPath = saveImgInSelectedFolder(mat, outputFolder);
                outputPathList.add(outputPath);
            }
        }

        return outputPathList;
    }


    // 处理文件上传并生成Mat对象以及文件名的Map
    private List<Mat> loadImages(List<MultipartFile> files) {
        List<Mat> matList = new ArrayList<>();
        // 处理文件上传逻辑
        if (files != null && !files.isEmpty()) {
            for (MultipartFile file : files) {
                String originalFilename = file.getOriginalFilename();
                // 将 MultipartFile 转换为 OpenCV 的 Mat 对象
                byte[] bytes;
                try {
                    bytes = file.getBytes();
                } catch (IOException e) {
                    log.error("Error reading file bytes", e);
                    continue;
                }
                MatOfByte matOfByte = new MatOfByte(bytes);
                // 转化为Mat并填充matAndFileNameMap
                Mat image = Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_COLOR);
                if (!image.empty()) {
                    matList.add(image);
                    matAndFileNameMap.put(image, originalFilename);
                } else {
                    log.error("Error loading image: {}", matOfByte);
                }
            }
            log.info("Files uploaded successfully.");
        } else {
            log.info("No files were uploaded.");
        }
        return matList;
    }

    private List<List<Mat>> groupImagesWithSIFT(List<Mat> images, double percentage) {
        List<List<Mat>> imageGroups = new ArrayList<>();
        List<MatOfKeyPoint> keyPointsList = new ArrayList<>();
        List<Mat> descriptorsList = new ArrayList<>();

        // 使用SIFT算法提取特征
        SIFT sift = SIFT.create(5000);// 保证速度的同时，提升准确度

        // 一边提取特征，一边进行匹配
        for (int i = 0; i < images.size(); i++) {
            Mat image = images.get(i);

            MatOfKeyPoint keyPoints = new MatOfKeyPoint();
            Mat descriptors = new Mat();
            sift.detectAndCompute(image, new Mat(), keyPoints, descriptors);

            keyPointsList.add(keyPoints);
            descriptorsList.add(descriptors);

            boolean foundGroup = false;

            for (List<Mat> group : imageGroups) {
                Mat referenceImage = group.get(0);
                int index = images.indexOf(referenceImage);
                Mat referenceDesc = descriptorsList.get(index);

                if (isSimilar(descriptors, referenceDesc, percentage)) {
                    group.add(image);
                    foundGroup = true;
                    break;
                }
            }

            if (!foundGroup) {
                List<Mat> newGroup = new LinkedList<>();
                newGroup.add(image);
                imageGroups.add(newGroup);
            }
        }

        return imageGroups;
    }

    private boolean isSimilar(Mat descriptors1, Mat descriptors2, double percentage) {

        // 进行特征匹配
        BFMatcher matcher = BFMatcher.create();
        // 使用BFMatcher进行特征匹配
        List<MatOfDMatch> knnMatches = new ArrayList<>();
        matcher.knnMatch(descriptors1, descriptors2, knnMatches, 2);
        int totalMatchesSize = knnMatches.size();
        log.info("TotalMatches:{}", totalMatchesSize);

        // 应用比率测试
        List<DMatch> goodMatches = new LinkedList<>();
        for (MatOfDMatch matOfDMatch : knnMatches) {
            DMatch[] matches = matOfDMatch.toArray();
            if (matches[0].distance < RATIO_THRESHOLD * matches[1].distance) {
                goodMatches.add(matches[0]);
            }
        }
        int goodMatchesSize = goodMatches.size();
        log.info("goodMatches:{}", goodMatchesSize);

        // 前面针对total小于5000的，保证good大于拟合曲线；后面针对total大于5000的，保证good大于指定数
        if (totalMatchesSize <= 5000) {
            double ratio = percentage * 1.2;
            return goodMatchesSize > ratio * adjustMinMatches(totalMatchesSize);
        }else {
            return goodMatchesSize > 350;
        }
    }

    private int adjustMinMatches(int totalMatches) {
        int minMatches;
        if (totalMatches <= 200) {
            minMatches = 60;
        } else if (totalMatches <= 2400) {
            minMatches = (int) (
                    - 57.7250290718273 * Math.pow(totalMatches, 0)
                    + 0.6585584320840243 * Math.pow(totalMatches, 1)
                    - 7.215483822166689E-4 * Math.pow(totalMatches, 2)
                    + 4.3686505888424346E-7 * Math.pow(totalMatches, 3)
                    - 1.3744289750440926E-10 * Math.pow(totalMatches, 4)
                    + 2.1036304261548967E-14 * Math.pow(totalMatches, 5)
                    - 1.1844434932044887E-18 * Math.pow(totalMatches, 6)) + 10;
        } else {
            minMatches = 340;
        }
        return minMatches;
    }

    // 修改 findClearestImage 方法
    private Mat findClearestImage(List<Mat> images) {
        double maxSharpness = Double.MIN_VALUE;
        Mat clearestImage = null;

        for (Mat image : images) {
            // 计算图像清晰度，使用前面提到的方法
            double sharpness = calculateSharpness(image);

            // 更新最清晰的图像
            if (sharpness > maxSharpness) {
                maxSharpness = sharpness;
                clearestImage = image.clone(); // 克隆图像，避免修改原始图像
                // 更新全局Map
                String originalFilename = matAndFileNameMap.get(image);
                matAndFileNameMap.remove(image);
                matAndFileNameMap.put(clearestImage, originalFilename);
            }
        }

        return clearestImage;
    }


    private double calculateSharpness(Mat image) {
        Mat gray = new Mat();
        Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

        Mat gradientX = new Mat();
        Mat gradientY = new Mat();
        Imgproc.Sobel(gray, gradientX, CvType.CV_32F, 1, 0, 3, 1, 0, Core.BORDER_DEFAULT);
        Imgproc.Sobel(gray, gradientY, CvType.CV_32F, 0, 1, 3, 1, 0, Core.BORDER_DEFAULT);

        Mat magnitude = new Mat();
        Core.magnitude(gradientX, gradientY, magnitude);

        Scalar score = Core.mean(magnitude);

        // 释放资源
        gray.release();
        gradientX.release();
        gradientY.release();
        magnitude.release();

        return score.val[0];
    }

}
